/************************************************************************
* mesh.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#include "graphics.h"
#include "array.h"

#include <string.h>

/* create a new mesh */
mesh_t *mesh_create(GLenum mode)
{
	mesh_t *m = malloc(sizeof(mesh_t));
	m->mode = mode;
	m->mat = NULL;
	m->col = NULL;
	m->v = array_create(ARRAY_TYPE_FLOAT);
	m->n = NULL;
	m->t = NULL;
	m->i = array_create(ARRAY_TYPE_INT);
	m->w = NULL;
	m->m = NULL;

	m->option = 0;

	m->vao.state = 0;

	m->bounds.max.x = 0.0;
	m->bounds.max.y = 0.0;
	m->bounds.max.z = 0.0;
	m->bounds.min.x = 0.0;
	m->bounds.min.y = 0.0;
	m->bounds.min.z = 0.0;

	return m;
}

/* destroy a mesh */
void mesh_free(mesh_t *m)
{
	if (m->col)
		free(m->col);
	if (m->v)
		array_free(m->v,1);
	if (m->n)
		array_free(m->n,1);
	if (m->t)
		array_free(m->t,1);
	if (m->i)
		array_free(m->i,1);
	if (m->w)
		array_free(m->w,1);

	if (m->vao.state) {
		if (m->vao.vertices)
			glDeleteBuffers(1,&m->vao.vertices);
		if (m->vao.normals)
			glDeleteBuffers(1,&m->vao.normals);
		if (m->vao.texcoords)
			glDeleteBuffers(1,&m->vao.texcoords);
		if (m->vao.indices)
			glDeleteBuffers(1,&m->vao.indices);
		if (m->vao.list)
			glDeleteVertexArrays(1,&m->vao.list);
	}

	free(m);
}

/* create a mesh for a material */
mesh_t *mesh_create_material(material_t *mat)
{
	mesh_t *m = mesh_create(GL_TRIANGLES);
	m->mat = mat;
	m->t = array_create(ARRAY_TYPE_FLOAT);
	m->n = array_create(ARRAY_TYPE_FLOAT);
	return m;
}

/* create a mesh for a colour */
mesh_t *mesh_create_colour(colour_t *c)
{
	mesh_t *m = mesh_create(GL_TRIANGLES);
	m->col = malloc(sizeof(colour_t));
	m->col->r = c->r;
	m->col->g = c->g;
	m->col->b = c->b;
	m->col->a = c->a;
	m->n = array_create(ARRAY_TYPE_FLOAT);
	return m;
}

/* duplicate a mesh */
mesh_t *mesh_copy(mesh_t *om)
{
	mesh_t *m = mesh_create(om->mode);
	if (om->mat)
		m->mat = om->mat;
	m->v = array_copy(om->v);
	if (om->n)
		m->n = array_copy(om->n);
	if (om->t)
		m->t = array_copy(om->t);
	m->i = array_copy(om->i);

	m->option = om->option;

	return m;
}

/* push a vertex onto a mesh */
int mesh_push_point(mesh_t *m, v3_t *v1)
{
	int i;
	for (i=0; i<m->v->length; i+=3) {
		if (
			v1->x == AFLOAT(m->v->data,i)
			&& v1->y == AFLOAT(m->v->data,i+1)
			&& v1->z == AFLOAT(m->v->data,i+2)
		) {
			array_push_int(m->i,i/3);
			return 0;
		}
	}
	array_push_int(m->i,i/3);
	array_push_float(m->v,v1->x);
	array_push_float(m->v,v1->y);
	array_push_float(m->v,v1->z);

	return 1;
}

/* push a poly point onto a mesh */
int mesh_push_polypoint(mesh_t *m, v3_t *v, v2_t *t)
{
	int i;
	int vi;
	int ti;
	for (i=0; (i*3)<m->v->length; i++) {
		vi = i*3;
		ti = i*2;
		if (
			v->x == AFLOAT(m->v->data,vi)
			&& v->y == AFLOAT(m->v->data,vi+1)
			&& v->z == AFLOAT(m->v->data,vi+2)
			&& t->x == AFLOAT(m->t->data,ti)
			&& t->y == AFLOAT(m->t->data,ti+1)
		) {
			array_push_int(m->i,i);
			return 0;
		}
	}
	array_push_int(m->i,i);
	array_push_v3t(m->v,v);
	array_push_v2t(m->t,t);

	return 1;
}

/* push a poly point with normal onto a mesh */
int mesh_push_polypoint_n(mesh_t *m, v3_t *v, v3_t *n, v2_t *t)
{
	int i;
	int vi;
	int ti;
	for (i=0; (i*3)<m->v->length; i++) {
		vi = i*3;
		ti = i*2;
		if (
			v->x == AFLOAT(m->v->data,vi)
			&& v->y == AFLOAT(m->v->data,vi+1)
			&& v->z == AFLOAT(m->v->data,vi+2)
			&& n->x == AFLOAT(m->n->data,vi)
			&& n->y == AFLOAT(m->n->data,vi+1)
			&& n->z == AFLOAT(m->n->data,vi+2)
			&& t->x == AFLOAT(m->t->data,ti)
			&& t->y == AFLOAT(m->t->data,ti+1)
		) {
			array_push_int(m->i,i);
			return 0;
		}
	}
	array_push_int(m->i,i);
	array_push_v3t(m->v,v);
	array_push_v3t(m->n,n);
	array_push_v2t(m->t,t);

	return 1;
}

/* push a poly onto a mesh */
void mesh_push_poly(mesh_t *m, v3_t *v1, v3_t *v2, v3_t *v3)
{
	if (mesh_push_point(m,v1)) {
		if (m->mat) {
			array_push_float(m->t,0);
			array_push_float(m->t,0);
		}
	}
	if (mesh_push_point(m,v2)) {
		if (m->mat) {
			array_push_float(m->t,0);
			array_push_float(m->t,1);
		}
	}
	if (mesh_push_point(m,v3)) {
		if (m->mat) {
			array_push_float(m->t,1);
			array_push_float(m->t,1);
		}
	}
}

/* push a quad onto a mesh */
void mesh_push_quad(mesh_t *m, v3_t *v1, v3_t *v2, v3_t *v3, v3_t *v4)
{
	int i = m->v->length/3;
	array_push_float(m->v,v1->x);
	array_push_float(m->v,v1->y);
	array_push_float(m->v,v1->z);
	array_push_float(m->v,v2->x);
	array_push_float(m->v,v2->y);
	array_push_float(m->v,v2->z);
	array_push_float(m->v,v3->x);
	array_push_float(m->v,v3->y);
	array_push_float(m->v,v3->z);
	array_push_float(m->v,v4->x);
	array_push_float(m->v,v4->y);
	array_push_float(m->v,v4->z);

	array_push_float(m->t,0);
	array_push_float(m->t,0);
	array_push_float(m->t,0);
	array_push_float(m->t,1);
	array_push_float(m->t,1);
	array_push_float(m->t,1);
	array_push_float(m->t,1);
	array_push_float(m->t,0);

	array_push_int(m->i,i);
	array_push_int(m->i,i+1);
	array_push_int(m->i,i+3);
	array_push_int(m->i,i+1);
	array_push_int(m->i,i+2);
	array_push_int(m->i,i+3);
}

/* calculate normals for a mesh */
void mesh_calc_normals(mesh_t *m)
{
	int i;
	GLuint k[9];
	v3_t v1;
	v3_t v2;
	v3_t v3;
	v3_t b1;
	v3_t b2;
	v3_t normal;
	int *con = alloca(m->v->length*sizeof(int));
	memset(con,0,m->v->length*sizeof(int));

	if (m->n) {
		m->n->length = 0;
	}else{
		m->n = array_create(ARRAY_TYPE_FLOAT);
	}

	if (m->mat && (m->mat->options&MATOPT_UPNORMAL) == MATOPT_UPNORMAL) {
		for (i=0; i<m->v->length; i+=3) {
			array_push_float(m->n,0);
			array_push_float(m->n,1.0);
			array_push_float(m->n,0);
		}
		return;
	}

	for (i=0; i<m->v->length; i++) {
		array_push_float(m->n,0);
	}

	for (i=0; i<m->i->length; i+=3) {
		k[0] = AUINT(m->i->data,i)*3;
		k[1] = k[0]+1;
		k[2] = k[0]+2;
		k[3] = AUINT(m->i->data,i+1)*3;
		k[4] = k[3]+1;
		k[5] = k[3]+2;
		k[6] = AUINT(m->i->data,i+2)*3;
		k[7] = k[6]+1;
		k[8] = k[6]+2;
		v1.x = AFLOAT(m->v->data,k[0]);
		v1.y = AFLOAT(m->v->data,k[1]);
		v1.z = AFLOAT(m->v->data,k[2]);
		v2.x = AFLOAT(m->v->data,k[3]);
		v2.y = AFLOAT(m->v->data,k[4]);
		v2.z = AFLOAT(m->v->data,k[5]);
		v3.x = AFLOAT(m->v->data,k[6]);
		v3.y = AFLOAT(m->v->data,k[7]);
		v3.z = AFLOAT(m->v->data,k[8]);

		b1.x = v2.x-v1.x;
		b1.y = v2.y-v1.y;
		b1.z = v2.z-v1.z;

		b2.x = v3.x-v1.x;
		b2.y = v3.y-v1.y;
		b2.z = v3.z-v1.z;

		vect_crossproduct(&b1,&b2,&normal);
		vect_normalise(&normal);

		con[k[0]] += 1;
		con[k[1]] += 1;
		con[k[2]] += 1;

		AFLOAT(m->n->data,k[0]) += normal.x;
		AFLOAT(m->n->data,k[1]) += normal.y;
		AFLOAT(m->n->data,k[2]) += normal.z;
		AFLOAT(m->n->data,k[3]) += normal.x;
		AFLOAT(m->n->data,k[4]) += normal.y;
		AFLOAT(m->n->data,k[5]) += normal.z;
		AFLOAT(m->n->data,k[6]) += normal.x;
		AFLOAT(m->n->data,k[7]) += normal.y;
		AFLOAT(m->n->data,k[8]) += normal.z;
	}

	for (i=0; i<m->n->length; i+=3) {
		if (con[i]>1) {
			AFLOAT(m->n->data,i) /= con[i];
			AFLOAT(m->n->data,i+1) /= con[i];
			AFLOAT(m->n->data,i+2) /= con[i];
		}
	}
}
